package com.thp.bigdata.webLog;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.junit.Test;
/**
 * 
 * 对web日志的清洗(简易版本)：
 * 我们先来单独看一下web日志里面的一条数据：
 * 	194.237.142.21 - - [18/Sep/2013:06:49:18 +0000] "GET /1.html HTTP/1.1" 304 0 "-" "Mozilla/4.0 (compatible;)"
 * 里面有ip地址，访问时间，使用GET还是POST访问，请求的页面，访问的状态
 * 我们现在就做简化处理。我们要知道一个用户在一个页面上停留的时间
 * 由于一个ip可以被多个用用户使用，于是我们区分用户的方式就是使用sessionID
 * 但是sessionID本身在上面的web日志里面的数据是没有的，是要我们自己生成的
 * 生成sessionID是有自己的规则的，
 * 不同的ip地址肯定不是同一个sessionID
 * 同一个ip地址，当请求的时间超过了30min，我们就把这条访问记录的sessionID设置成不一样
 * 
 * 所以首先我们要将所有的数据根据IP分离出来
 * 然后根据IP生成sessionID
 * 生成sessionID之后，使用sessionID作为主键，存在HashMap中，然后返回给用户
 * 
 * @author 汤小萌
 *
 */
public class WashWebLog {

	public static void main(String[] args) throws IOException {

		// 得到ip对应的SessionBean 的List集合      
		Map<String, List<SessionBean>> IPListMap = getIPSessionBeanMap();
		
		
		/**
		 * 之所以要对每个List中色SessionBean根据时间先后排序，
		 * 因为我们需要根据时间来计算sessionID
		 */
		// 对IPListMap每一个键值对数据里面的List的SessionBean按照时间进行排序
		sortByDate(IPListMap);
		
		/**
		 * 到现在为止，我们还只是跟IP进行区分
		 * 我们还没有生成sessionID
		 * 我们需要生成sessionID,才能根据sessionID来进行区分 
		 */
		
		// 生成sessionId
		makeSessionId(IPListMap);

		/*
		 * for(Entry<String, List<SessionBean>> entrySet : IPListMap.entrySet())
		 * { System.out.println("ip : " + entrySet.getKey() + " -- " +
		 * entrySet.getValue()); }
		 */

		Map<String, List<SessionBean>> sessionIdListMap = new HashMap<String, List<SessionBean>>();

		/**
		 * 经过了上面的步骤，每一个IP里面的每一个SessionBean 都生成了sessionID
		 * 现在我们就需要将所有的数据都拿出来，进行重新洗牌
		 * 我们需要的是根据sessionID来进行划分
		 */
		for (Entry<String, List<SessionBean>> entrySet : IPListMap.entrySet()) {
			List<SessionBean> sessionBeans = entrySet.getValue();
			for (SessionBean sessionBean : sessionBeans) {
				// 千万要注意这些Map的区别含义，别搞混了，搞混了，那么清洗出来的数据都是错的 注意获取的是sessionId 而不是 IP
				// 如果是IP 的话，那么会
				// 一直只有一条数据
				List<SessionBean> list = sessionIdListMap.get(sessionBean.getSessionId());
				if (list == null) {
					list = new ArrayList<SessionBean>();
				}
				list.add(sessionBean);
				// 此时的key 变成sessionID了
				sessionIdListMap.put(sessionBean.getSessionId(), list);
			}
		}

		/**
		 * 将数据格式化地显示出来
		 */
		for (Entry<String, List<SessionBean>> entrySet : sessionIdListMap.entrySet()) {
			String sessionId = entrySet.getKey();
			List<SessionBean> sessionBeans = entrySet.getValue();
			SessionBean start = sessionBeans.get(0);
			SessionBean end = sessionBeans.get(sessionBeans.size() - 1);
			long differ = end.getDate().getTime() - start.getDate().getTime();

			String res = sessionId + "\t" + start.getIp() + "\t" + start.getDate() + "\t" + end.getDate() + "\t"
					+ start.getUrl() + "\t" + end.getUrl() + "\t" + (differ / 1000);
			System.out.println(res);
		}
	}

	/**
	 * 对List里面的SessionBean 按照时间排序之后，生成SessionId
	 * 
	 * @param IPListSortedMap
	 */
	private static void makeSessionId(Map<String, List<SessionBean>> IPListSortedMap) {
		for (Entry<String, List<SessionBean>> entrySet : IPListSortedMap.entrySet()) {
			List<SessionBean> sessionBeans = entrySet.getValue();
			if (sessionBeans.size() == 1) { // 长度等于1时   这个ip地址就只有一条数据，那么肯定就是直接生成sessionID
				SessionBean sessionBean = sessionBeans.get(0);
				sessionBean.setSessionId(getSessionId(sessionBean.getIp()));
				sessionBean.setOrder(1);
			}
			// 当长度大于1时     这个ip对应的访问记录有多条
			for (int i = 0; i < sessionBeans.size() - 1; i++) {
				SessionBean sb1 = sessionBeans.get(i);  // 获取这个ip  i 时刻的访问记录数据
				SessionBean sb2 = sessionBeans.get(i + 1); // 获取这个ip  i+1 时刻的访问记录 
				// 同一个session的时候   (先判断是不是同属于一个session  --  根据访问时间是不是在30分钟之内)
				if (isSameSession(sb1, sb2)) {
					// 早的那个可能之前就已经设置了sessionId 就是上面的步骤设置了 那么order也一同设置了
					if (sb1.getSessionId() != null) {
						sb2.setSessionId(sb1.getSessionId());
						sb2.setOrder(sb1.getOrder() + 1);
					} else {
						// 连个sessionID都没有生成sessionID,那么就生成一个，连个SessionBean使用同一个
						sb1.setSessionId(getSessionId(sb1.getIp()));
						sb1.setOrder(1);
						sb2.setSessionId(sb1.getSessionId());
						// order后一个访问记录需要  + 1
						sb2.setOrder(sb1.getOrder() + 1);
					}
				} else {
					// 不是同一个session
					if (sb1.getSessionId() != null) {  // 前一个sessionBean经过上面的步骤已经设置了sessionID
						sb2.setSessionId(getSessionId(sb2.getIp()));
						sb2.setOrder(1); // sb2是来自新的一个session, order要设置为1
					} else {  // 连个SessionBean都没有设置sessionID
						sb1.setSessionId(getSessionId(sb1.getIp()));
						sb1.setOrder(1);
						sb2.setSessionId(getSessionId(sb2.getIp()));
						sb2.setOrder(1);
					}
				}

			}
		}
	}

	/**
	 * 根据SessionBean 的时间判断是不是同一个session
	 * 
	 * @param s1
	 * @param s2
	 * @return
	 */
	private static boolean isSameSession(SessionBean s1, SessionBean s2) {
		long time1 = s1.getDate().getTime();
		long time2 = s2.getDate().getTime();
		// session 时间 0 - 30 分钟
		long differ = time2 - time1;
		if (differ >= 0 && differ <= (1000 * 60 * 30)) {
			return true;
		}
		return false;
	}

	/**
	 * 生成sessionId的方法
	 */
	private static String getSessionId(String ip) {
		// Integer.parseInt(ip);
		String[] fields = ip.split("\\.");
		StringBuffer sb = new StringBuffer();
		for (String field : fields) {
			sb.append(field);
		}
		long longIP = Long.parseLong(sb.toString());
		long nanoTime = System.nanoTime();
		return "" + longIP + nanoTime;
	}

	/**
	 * 对IPListMap里的每一条数据的List中的SessionBean按照时间进行排序
	 * 
	 * @param IPListMap
	 */
	private static void sortByDate(Map<String, List<SessionBean>> IPListMap) {
		for (Entry<String, List<SessionBean>> entrySet : IPListMap.entrySet()) {
			List<SessionBean> list = entrySet.getValue();
			// 根据自己定义的方法来进行排序
			Collections.sort(list, new Comparator<SessionBean>() {
				@Override
				public int compare(SessionBean o1, SessionBean o2) {
					Date date1 = o1.getDate();
					Date date2 = o2.getDate();
					return date1.before(o2.getDate()) ? -1 : 1;
				}

			});
		}
	}
	
	/**
	 * 这个方法主要的功能：
	 *   1，去读取web日志数据 按照行去读取
	 *   2. 按照行读取sweb日志的时候，还需要将关键的数据获取出来，存放在sessionBean中
	 *   3.使用IP作为key,这个ip所对应的其他的数据都存放在这个IP所对应的List集合中
	 * @return
	 */
	private static Map<String, List<SessionBean>> getIPSessionBeanMap() {
		Map<String, List<SessionBean>> map1 = new HashMap<String, List<SessionBean>>();
		BufferedReader bufferedReader = null;
		try {
			bufferedReader = new BufferedReader(new
			FileReader(DemoTest.class.getResource("access.log.fensi").getPath()));
			// bufferedReader = new BufferedReader(new FileReader(DemoTest.class.getResource("data.dat").getPath()));
			String line = null;
			while ((line = bufferedReader.readLine()) != null) {
				// 数字 + . (连续出现三次) + 最后一次出现的还是数字 -- \\d+ 代表的是多个数字
				String ipRegex = "(\\d+\\.){3}\\d+";
				// [ + 任何单字符 + 数字(一个或者多个) + ]
				String dateRegex = "\\[.+\\d+\\]";
				// 首先是 GET 或者 POST (只要有一个出现就可以匹配) + 空格 + 非空格的字符(0次或者多次)
				String urlRegex = "(POST|GET){1}\\s(\\S)*\\s";

				// 匹配IP
				String ip = getConByRegex(line, ipRegex);
				// 匹配日期
				String date = getConByRegex(line, dateRegex);
				// 匹配url
				String url = getConByRegex(line, urlRegex);
				if (ip != null && date != null && url != null) {
					// 封装SessionBean
					SessionBean sessionBean = new SessionBean();
					sessionBean.setIp(ip);
					sessionBean.setDate(parDateFromStr(date));
					sessionBean.setUrl(url);
					List<SessionBean> list = map1.get(ip);
					if (list == null) { // 如果HashMap中这个ip已经存在就在这个ip对应的List中增加SessionBean
						list = new ArrayList<SessionBean>();   // 如果这个ip在HashMap中不存在，那么就为这个ip新建一个List
					}
					list.add(sessionBean);
					map1.put(ip, list);
				}
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (bufferedReader != null) {
				try {
					bufferedReader.close();
				} catch (IOException e) {
					e.printStackTrace();
				} finally {
					bufferedReader = null;
				}
			}
		}

		return map1;
	}

	/**
	 * 将日期格式的字符串转换为Date类型
	 * 
	 * @param dataStr
	 * @return
	 */
	private static Date parDateFromStr(String dateStr) {
		// [19/Sep/2013:04:38:11 +0000]
		String substring = dateStr.substring(1, dateStr.length() - 1);
		// 注意是有 三个 M ， 后面要加上Locale.US 否则会报错
		SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MMM/yyyy:HH:mm:ss", Locale.US);
		try {
			return dateFormat.parse(substring);
		} catch (ParseException e) {
			e.printStackTrace();
		}
		System.err.println("< 日期解析异常 >");
		return null;
	}

	/**
	 * 匹配字符串
	 * 
	 * @param line
	 * @param regex
	 * @return
	 */
	private static String getConByRegex(String line, String regex) {
		Pattern compile = Pattern.compile(regex);
		Matcher matcher = compile.matcher(line);
		while (matcher.find()) {
			// 返回的是符合正则规则的字符串
			return matcher.group();
		}
		return null;
	}

	@Test
	public void testRegex() {

		String line = "GETGETPOST /wp-content/uploads/2013/07/rstudio-git3.png HTTP/1.1";

		// String ipRegex = "(\\d+\\.){3}\\d+";
		// String regex ="\\[.+\\d+\\]";
		String regex = "(POST|GET){1}\\s(\\S)*\\s";

		Pattern compile = Pattern.compile(regex);

		Matcher matcher = compile.matcher(line);

		while (matcher.find()) {
			System.out.println(matcher.group());
			return;
		}

		System.out.println("没有匹配");

		return;
	}

	@Test
	public void testLineData() {
		String lineData = "194.237.142.21 - - [18/Sep/2013:06:49:18 +0000] \"GET /wp-content/uploads/2013/07/rstudio-git3.png HTTP/1.1\" 304 0 \"-\" \"Mozilla/4.0 (compatible;)\"";
		String[] fields = lineData.split(" ");
		for (String field : fields) {
			System.out.println(field);
		}

	}

}
